VIVO = 1
MUERTO = 0

class Particula():
    def __init__(self):
        self.value = 1
    
    def __repr__(self):
        return 'particula'
        
class Celda():
    
    def __init__(self, pressure=0, new_pressure=0):
        self.pressure = pressure
        self.new_pressure = new_pressure
        self.n = pressure
        self.new_n = new_pressure
        self.s = pressure
        self.new_s = new_pressure
        self.e = pressure
        self.new_e = new_pressure
        self.o = pressure
        self.new_o = new_pressure
        self.ne = pressure
        self.new_ne = new_pressure
        self.se = pressure
        self.new_se = new_pressure
        self.no = pressure
        self.new_no = new_pressure
        self.so = pressure
        self.new_so = new_pressure
        
    
    def get_pressure(self):
        return self.pressure

    def getN(self):
        return self.n    
    def getS(self):
        return self.s
    def getE(self):
        return self.e
    def getO(self):
        return self.o
    def getNE(self):
        return self.ne    
    def getSE(self):
        return self.se
    def getNO(self):
        return self.no    
    def getSO(self):
        return self.so
    
    def get_value(self, punto):
        switch = {'n':self.getN, 's':self.getS, 'e':self.getE, 'o':self.getO, 
            'se':self.getSE, 'ne':self.getNE, 'no':self.getNO, 'so':self.getSO}
        return switch[punto]()
    def set_value(self, value, punto):
        switch = {'n':self.n, 's':self.s, 'e':self.e, 'o':self.o, 'se':self.se, 
                'ne':self.ne, 'no':self.no, 'so':self.so}
        switch[punto] = value
        
    def update_value(self, value, punto):
        '''Actulaza en 'value' el valor de la presion en el punto 'punto'. '''
        switch = {'n':self.new_n, 's':self.new_s, 'e':self.new_e, 'o':self.new_o,
            'se':self.new_se, 'ne':self.new_ne, 'no':self.new_no, 'so':self.new_so}
        switch[punto] += value
        
    
    def get_new_pressure(self):
        return self.new_pressure
    
    def set_all(self, value):
        self.pressure = value
        self.new_pressure = value
        self.n = value
        self.s = value
        self.e = value
        self.o = value
        self.ne = value
        self.so = value
        self.no = value
        self.se = value
    
    def update(self):
        self.pressure = self.new_pressure
        self.n = self.new_n
        self.s = self.new_s
        self.e = self.new_e
        self.o = self.new_o
        self.ne = self.new_ne
        self.so = self.new_so
        self.no = self.new_no
        self.se = self.new_se
        
    def __repr__(self):
        ret = ''
        ret += 'Presion: %s\n'%(self.pressure)
        ret += 'N: %s\n'%(self.n)
        ret += 'S %s\n'%(self.s)
        ret += 'E: %s\n'%(self.e)
        ret += 'O: %s\n'%(self.o)
        ret += 'NE: %s\n'%(self.ne)
        ret += 'NO: %s\n'%(self.no)
        ret += 'SE: %s\n'%(self.se)
        ret += 'SO: %s\n'%(self.so)
        
        return ret

def clamp(value, max, min):
        '''
        Funcion clamp, devuelve value si value esta entre min y max, devuelve min o max si no.
        '''
        if value<min:
            return min
        if value>max:
            return max
        return value


class Matrix():
    """Creo una matriz que sera una lista de listas
    la lista inicial tendra en cada posicion las filas
    de la matriz:
    Ejemplo de matriz 4xn
        [fila1, fila2, fila3, fila4]
    Donde fila_i es una lista de longitud n"""
    
    def __init__(self, rows, cols):
        self.cols = cols
        self.rows = rows
        
        # Inicializo matriz
        self.matrix = []
        
        for i in range(rows):
            # Creo una fila nuevo
            row = []
            
            for j in range(cols):
                # Lleno la fila con 0 en cada celda
                celda = Celda()
                row.append(celda)
            
            self.matrix.append(row)
        
    def get_value(self, row, col, punto_cardinal):
        ''' Devuelve el valor de la celda en (row,col)'''
        
        # Si se pide una celda fuera de la matriz
        if self.rows < row or row < 1 or self.cols < col or col < 1:
            
            # Por defecto devuelvo el valor de una celda muerta
            return MUERTO
        
        #switch = {'n':getN, 's':getS, 'e':getE, 'o':getO, 'se':getSE, 'ne':getNE, 'no':getNO, 'so':getSO}
        
        return (self.matrix[row-1][col-1]).get_value(punto_cardinal)
        #return (self.matrix[row-1][col-1]).switch[punto_cardinal]()
    def get_total(self, row, col):
        puntos = ['n', 's', 'e', 'o', 'se', 'ne', 'no', 'so']
        ret = 0
        for punto in puntos:
            ret += self.get_value(row,col,punto)
            
        return ret
    def get_cell(self, row, col):
        if self.rows < row or row < 1 or self.cols < col or col < 1:
            # Por defecto devuelvo el valor de una celda muerta
            return Celda()
        return self.matrix[row-1][col-1]

    def update_cell(self, row, col, value):
        c = self.get_cell(row,col)
        c.set_all = value
        
        
    def set_value(self, row, col, value):
        ''' Le asigna un valor a la presion'''
        # Si se pide una celda fuera de la matriz
        if self.rows < row or row < 1 or self.cols < col or col < 1:
            print 'Celda fuera de alcance'
            return -1
        
        self.matrix[row-1][col-1].pressure = value
        
    def vecinos(self, row, col):
        ''' Devuelve una lista con los vecinos de la vecindad de Moore. Son los 
        vecinos de los puntos cardinales y las diagonales. Es una lista de celdas'''
        
        ret = {'n':2 ,'s': 2,'e': 2, 'o': 2, 'ne':2 ,'se':2 , 'no':2 ,'so':2}
        
        # Obtengo vecinos vivos
        #for i in range(row-1, row+2):
        #    for j in range(col-1, col+2):
        #        if (i, j) != (row,col) :#and self.get_value(i,j) == VIVO:
        #            ret.append(self.get_cell(i,j))
        
        # Vecino del Norte
        ret['n'] = self.get_cell(i-1,j)
        # Vecino del Sur
        ret['s'] = self.get_cell(i+1,j)
        # Vecino del Este
        ret['e'] = self.get_cell(i,j+1)
        # Vecino del Oeste
        ret['o'] = self.get_cell(i,j-1)
        
        # Vecino del Norte-Este
        ret['ne'] = self.get_cell(i-1,j+1)
        # Vecino del Sur-Este
        ret['se'] = self.get_cell(i+1,j+1)
        # Vecino del Norte-Oeste
        ret['no'] = self.get_cell(i-1,j-1)
        # Vecino del Sur-Oeste
        ret['so'] = self.get_cell(i+1,j-1)
        
        return ret
    
    def vecinos_neumann(self, row, col):
        ''' Devuelve una lista con los vecinos de la vecindad de Neumann. Son
        los vecinos de los 4 puntos cardinales. Es una lista de celdas.
        '''
        ret = []
        
        # Obtengo vecinos 
        # Vecino del Norte
        ret.append(self.get_cell(i-1,j))
        # Vecino del Sur
        ret.append(self.get_cell(i+1,j))
        # Vecino del Este
        ret.append(self.get_cell(i,j-1))
        # Vecino del Oeste
        ret.append(self.get_cell(i,j+1))
        
        return ret
    
    def rules_pressure(self):

        # Devuelvo una lista con las celdas que cambiaron
        changes = []
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
            
                # Obtengo los vecinos de la celda
                vecinos = self.vecinos(i,j)
                old_pressure = self.get_cell(i,j).new_pressure 
                for vecino in vecinos.values():
                
                    # Calculo la diferencia de presion
                    dpres = self.get_value(i,j) - vecino.get_pressure()
                
                    '''Calculamos el flujo como una fraccion de la diferencia de presion.
                    0.12 es un numero magico que indica la fluidez del medio. Un valor muy chico
                    hace que la presion se propague mas lentamente.
                    Usamos la funcion clamp para evitar valores negativos en el nodo.
                    Se hace que el flujo no produsca una presion negativa'''
                    flow=clamp(0.12*dpres, self.get_value(i,j)/8.0, -vecino.get_pressure()/8.0)
                    
                    #Actualizamos la presion (usando new_pressure en lugar de pressure) substrayendo
                    #presion del nodo actual y agregandola a su vecino. Un flujo negativo implica
                    #que la presion del nodo actual aumenta y la del vecino disminuye.
                    new_pressure = self.get_cell(i,j).new_pressure - flow
                    self.get_cell(i,j).new_pressure = new_pressure 
                    new_pressure = vecino.new_pressure + flow
                    vecino.new_pressure = new_pressure
                    
                # Actualizo puntos cardinales 
                puntos = ['n', 's', 'e', 'o', 'ne','se', 'no','so']
                for punto in puntos:
                    celda = serf.get_cell(i,j)
                    # Obtengo valor de la celda que estoy viendo
                    valor = celda.get_value(punto)
                    flow = 0.15
                    value = flow * valor
                    
                    '''Para el vecino de cada punto cardinal, actualizo su presion
                    pero en el punto cardinal que corresponde'''
                    vecinos[punto].update_value(value, punto)
                    
                    # Ahora se le resta a la celda que estoy viendo la cantidad 'value'
                    # que es lo que se transfirio a la celda vecina
                    celda.update_value(-value, punto)
                    
                    
                if old_pressure != self.get_cell(i,j).new_pressure and (i,j) not in changes:
                        if self.get_cell(i,j).new_pressure> 0.1 or self.get_cell(i,j).new_pressure < -0.1:
                            # Actualizo lista de celdas que cambiaron
                            changes.append((i,j))
                            #print 'You can update the performance'

        return changes

    def update_pressure(self):
        changes = self.rules_pressure()
        #for pos in changes:
        #    self.get_cell(pos[0],pos[1]).update()
            #print 'Changes (%s,%s): %s'%(pos[0],pos[1],self.get_cell(pos[0],pos[1]))
        #print 'Update loops: %s'%len(changes)
        
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
                self.get_cell(i,j).update()
            
        return 

    def rules(self, row, col):
        vivir = False
        alive = self.alive(row,col)
        vecinos = len(self.vecinos(row,col)) 
    
        if vecinos < 2 and alive:
            #print "Rule 1 antes VIVA=%s ahora VIVA Celda (%s,%s)" %(alive, row,col)
            vivir = False
        elif vecinos > 3 and alive:
            #print "Rule 2 antes VIVA=%s ahora VIVA Celda (%s,%s)" %(alive, row,col)
            vivir = False
        elif (2 == vecinos or vecinos   == 3) and alive:
            #print "Rule 3 antes VIVA=%s ahora VIVA Celda (%s,%s)" %(alive, row,col)
            vivir = True
        elif vecinos == 3 and not alive:
            #print "Rule 4 antes VIVA=%s ahora VIVA Celda (%s,%s)" %(alive, row,col)
            vivir = True
        
        return vivir
    
    def rules_hydro(self):
        
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
            
                # Obtengo los vecinos de la celda
                vecinos = self.vecinos_neumann(i,j)
                
                # Hacer update de particulas por punto cardinal
                # quitar de uno para sumar a otro y si chocan que se dispersen
                for vecino in vecinos:
                
                    # Calculo la diferencia de presion
                    dpres = self.get_value(i,j) - vecino.get_pressure()
        
        return
    def update(self):
        new = Matrix(self.rows, self.cols)
    
    
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
                vivir = self.rules( i, j)
                if vivir:
                    new.set_value(i,j, VIVO)
        
        return new

    
    def alive(self, row, col):
        ''' Deprecated: Dice si el valor de la celda en (i,j) es 1'''
        return self.get_value(row,col) == VIVO
    
    def __repr__(self):
        
        ret = ''
        for i in range(self.rows):
            ret += 'Fila %s = %s \n'%(i+1,self.matrix[i])
        
        return ret
        